/*
 * Copyright 1999-2012 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.cobar.server.parser;

import com.alibaba.cobar.parser.util.CharTypes;
import com.alibaba.cobar.parser.util.ParseUtil;

/**
 * @author xianmao.hexm 2011-5-7 下午01:23:18
 */
public final class ServerParseSelect {

    public static final int OTHER = -1;
    public static final int VERSION_COMMENT = 1;
    public static final int DATABASE = 2;
    public static final int USER = 3;
    public static final int LAST_INSERT_ID = 4;
    public static final int IDENTITY = 5;
    public static final int VERSION = 6;

    private static final char[] _VERSION_COMMENT = "VERSION_COMMENT".toCharArray();
    private static final char[] _IDENTITY = "IDENTITY".toCharArray();
    private static final char[] _LAST_INSERT_ID = "LAST_INSERT_ID".toCharArray();
    private static final char[] _DATABASE = "DATABASE()".toCharArray();

    public static int parse(String stmt, int offset) {
        int i = offset;
        for (; i < stmt.length(); ++i) {
            switch (stmt.charAt(i)) {
            case ' ':
                continue;
            case '/':
            case '#':
                i = ParseUtil.comment(stmt, i);
                continue;
            case '@':
                return select2Check(stmt, i);
            case 'D':
            case 'd':
                return databaseCheck(stmt, i);
            case 'L':
            case 'l':
                return lastInsertCheck(stmt, i);
            case 'U':
            case 'u':
                return userCheck(stmt, i);
            case 'V':
            case 'v':
                return versionCheck(stmt, i);
            default:
                return OTHER;
            }
        }
        return OTHER;
    }

    // SELECT VERSION
    private static int versionCheck(String stmt, int offset) {
        if (stmt.length() > offset + "ERSION".length()) {
            char c1 = stmt.charAt(++offset);
            char c2 = stmt.charAt(++offset);
            char c3 = stmt.charAt(++offset);
            char c4 = stmt.charAt(++offset);
            char c5 = stmt.charAt(++offset);
            char c6 = stmt.charAt(++offset);
            if ((c1 == 'E' || c1 == 'e') && (c2 == 'R' || c2 == 'r') && (c3 == 'S' || c3 == 's')
                    && (c4 == 'I' || c4 == 'i') && (c5 == 'O' || c5 == 'o') && (c6 == 'N' || c6 == 'n')) {
                while (stmt.length() > ++offset) {
                    switch (stmt.charAt(offset)) {
                    case ' ':
                    case '\t':
                    case '\r':
                    case '\n':
                        continue;
                    case '(':
                        return versionParenthesisCheck(stmt, offset);
                    default:
                        return OTHER;
                    }
                }
            }
        }
        return OTHER;
    }

    // SELECT VERSION (
    private static int versionParenthesisCheck(String stmt, int offset) {
        while (stmt.length() > ++offset) {
            switch (stmt.charAt(offset)) {
            case ' ':
            case '\t':
            case '\r':
            case '\n':
                continue;
            case ')':
                while (stmt.length() > ++offset) {
                    switch (stmt.charAt(offset)) {
                    case ' ':
                    case '\t':
                    case '\r':
                    case '\n':
                        continue;
                    default:
                        return OTHER;
                    }
                }
                return VERSION;
            default:
                return OTHER;
            }
        }
        return OTHER;
    }

    /**
     * <code>SELECT LAST_INSERT_ID() AS id, </code>
     * 
     * @param offset index of 'i', offset == stmt.length() is possible
     * @return index of ','. return stmt.length() is possible. -1 if not alias
     */
    private static int skipAlias(String stmt, int offset) {
        offset = ParseUtil.move(stmt, offset, 0);
        if (offset >= stmt.length())
            return offset;
        switch (stmt.charAt(offset)) {
        case '\'':
            return skipString(stmt, offset);
        case '"':
            return skipString2(stmt, offset);
        case '`':
            return skipIdentifierEscape(stmt, offset);
        default:
            if (CharTypes.isIdentifierChar(stmt.charAt(offset))) {
                for (; offset < stmt.length() && CharTypes.isIdentifierChar(stmt.charAt(offset)); ++offset);
                return offset;
            }
        }
        return -1;
    }

    /**
     * <code>`abc`d</code>
     * 
     * @param offset index of first <code>`</code>
     * @return index of 'd'. return stmt.length() is possible. -1 if string
     *         invalid
     */
    private static int skipIdentifierEscape(String stmt, int offset) {
        for (++offset; offset < stmt.length(); ++offset) {
            if (stmt.charAt(offset) == '`') {
                if (++offset >= stmt.length() || stmt.charAt(offset) != '`') {
                    return offset;
                }
            }
        }
        return -1;
    }

    /**
     * <code>"abc"d</code>
     * 
     * @param offset index of first <code>"</code>
     * @return index of 'd'. return stmt.length() is possible. -1 if string
     *         invalid
     */
    private static int skipString2(String stmt, int offset) {
        int state = 0;
        for (++offset; offset < stmt.length(); ++offset) {
            char c = stmt.charAt(offset);
            switch (state) {
            case 0:
                switch (c) {
                case '\\':
                    state = 1;
                    break;
                case '"':
                    state = 2;
                    break;
                }
                break;
            case 1:
                state = 0;
                break;
            case 2:
                switch (c) {
                case '"':
                    state = 0;
                    break;
                default:
                    return offset;
                }
                break;
            }
        }
        if (offset == stmt.length() && state == 2) {
            return stmt.length();
        }
        return -1;
    }

    /**
     * <code>'abc'd</code>
     * 
     * @param offset index of first <code>'</code>
     * @return index of 'd'. return stmt.length() is possible. -1 if string
     *         invalid
     */
    private static int skipString(String stmt, int offset) {
        int state = 0;
        for (++offset; offset < stmt.length(); ++offset) {
            char c = stmt.charAt(offset);
            switch (state) {
            case 0:
                switch (c) {
                case '\\':
                    state = 1;
                    break;
                case '\'':
                    state = 2;
                    break;
                }
                break;
            case 1:
                state = 0;
                break;
            case 2:
                switch (c) {
                case '\'':
                    state = 0;
                    break;
                default:
                    return offset;
                }
                break;
            }
        }
        if (offset == stmt.length() && state == 2) {
            return stmt.length();
        }
        return -1;
    }

    /**
     * <code>SELECT LAST_INSERT_ID() AS id</code>
     * 
     * @param offset index of first ' ' after LAST_INSERT_ID(), offset ==
     *            stmt.length() is possible
     * @return index of 'i'. return stmt.length() is possible
     */
    public static int skipAs(String stmt, int offset) {
        offset = ParseUtil.move(stmt, offset, 0);
        if (stmt.length() > offset + "AS".length()
                && (stmt.charAt(offset) == 'A' || stmt.charAt(offset) == 'a')
                && (stmt.charAt(offset + 1) == 'S' || stmt.charAt(offset + 1) == 's')
                && (stmt.charAt(offset + 2) == ' ' || stmt.charAt(offset + 2) == '\r'
                        || stmt.charAt(offset + 2) == '\n' || stmt.charAt(offset + 2) == '\t'
                        || stmt.charAt(offset + 2) == '/' || stmt.charAt(offset + 2) == '#')) {
            offset = ParseUtil.move(stmt, offset + 2, 0);
        }
        return offset;
    }

    /**
     * @param offset <code>stmt.charAt(offset) == first 'L' OR 'l'</code>
     * @return index after LAST_INSERT_ID(), might equals to length. -1 if not
     *         LAST_INSERT_ID
     */
    public static int indexAfterLastInsertIdFunc(String stmt, int offset) {
        if (stmt.length() >= offset + "LAST_INSERT_ID()".length()) {
            if (ParseUtil.compare(stmt, offset, _LAST_INSERT_ID)) {
                offset = ParseUtil.move(stmt, offset + _LAST_INSERT_ID.length, 0);
                if (offset + 1 < stmt.length() && stmt.charAt(offset) == '(') {
                    offset = ParseUtil.move(stmt, offset + 1, 0);
                    if (offset < stmt.length() && stmt.charAt(offset) == ')') {
                        return ++offset;
                    }
                }
            }
        }
        return -1;
    }

    /**
     * @param offset
     *            <code>stmt.charAt(offset) == first '`' OR 'i' OR 'I' OR '\'' OR '"'</code>
     * @return index after identity or `identity` or "identity" or 'identity',
     *         might equals to length. -1 if not identity or `identity` or
     *         "identity" or 'identity'
     */
    public static int indexAfterIdentity(String stmt, int offset) {
        char first = stmt.charAt(offset);
        switch (first) {
        case '`':
        case '\'':
        case '"':
            if (stmt.length() < offset + "identity".length() + 2) {
                return -1;
            }
            if (stmt.charAt(offset + "identity".length() + 1) != first) {
                return -1;
            }
            ++offset;
            break;
        case 'i':
        case 'I':
            if (stmt.length() < offset + "identity".length()) {
                return -1;
            }
            break;
        default:
            return -1;
        }
        if (ParseUtil.compare(stmt, offset, _IDENTITY)) {
            offset += _IDENTITY.length;
            switch (first) {
            case '`':
            case '\'':
            case '"':
                return ++offset;
            }
            return offset;
        }
        return -1;
    }

    /**
     * SELECT LAST_INSERT_ID()
     */
    static int lastInsertCheck(String stmt, int offset) {
        offset = indexAfterLastInsertIdFunc(stmt, offset);
        if (offset < 0) {
            return OTHER;
        }
        offset = skipAs(stmt, offset);
        offset = skipAlias(stmt, offset);
        if (offset < 0) {
            return OTHER;
        }
        offset = ParseUtil.move(stmt, offset, 0);
        if (offset < stmt.length()) {
            return OTHER;
        }
        return LAST_INSERT_ID;
    }

    /**
     * select @@identity<br/>
     * select @@identiTy aS iD
     */
    static int identityCheck(String stmt, int offset) {
        offset = indexAfterIdentity(stmt, offset);
        if (offset < 0) {
            return OTHER;
        }
        offset = skipAs(stmt, offset);
        offset = skipAlias(stmt, offset);
        if (offset < 0) {
            return OTHER;
        }
        offset = ParseUtil.move(stmt, offset, 0);
        if (offset < stmt.length()) {
            return OTHER;
        }
        return IDENTITY;
    }

    static int select2Check(String stmt, int offset) {
        if (stmt.length() > ++offset && stmt.charAt(offset) == '@') {
            if (stmt.length() > ++offset) {
                switch (stmt.charAt(offset)) {
                case 'V':
                case 'v':
                    return versionCommentCheck(stmt, offset);
                case 'i':
                case 'I':
                    return identityCheck(stmt, offset);
                default:
                    return OTHER;
                }
            }
        }
        return OTHER;
    }

    /**
     * SELECT DATABASE()
     */
    static int databaseCheck(String stmt, int offset) {
        int length = offset + _DATABASE.length;
        if (stmt.length() >= length && ParseUtil.compare(stmt, offset, _DATABASE)) {
            if (stmt.length() > length && stmt.charAt(length) != ' ') {
                return OTHER;
            } else {
                return DATABASE;
            }
        }
        return OTHER;
    }

    /**
     * SELECT USER()
     */
    static int userCheck(String stmt, int offset) {
        if (stmt.length() > offset + 5) {
            char c1 = stmt.charAt(++offset);
            char c2 = stmt.charAt(++offset);
            char c3 = stmt.charAt(++offset);
            char c4 = stmt.charAt(++offset);
            char c5 = stmt.charAt(++offset);
            if ((c1 == 'S' || c1 == 's') && (c2 == 'E' || c2 == 'e') && (c3 == 'R' || c3 == 'r') && (c4 == '(')
                    && (c5 == ')') && (stmt.length() == ++offset || ParseUtil.isEOF(stmt.charAt(offset)))) {
                return USER;
            }
        }
        return OTHER;
    }

    /**
     * SELECT @@VERSION_COMMENT
     */
    static int versionCommentCheck(String stmt, int offset) {
        int length = offset + _VERSION_COMMENT.length;
        if (stmt.length() >= length && ParseUtil.compare(stmt, offset, _VERSION_COMMENT)) {
            if (stmt.length() > length && stmt.charAt(length) != ' ') {
                return OTHER;
            } else {
                return VERSION_COMMENT;
            }
        }
        return OTHER;
    }

}
